#pragma once

#include <iostream>

#include <cstring>
#include <cstdlib>
#include <cerrno>
#include <functional>
#include <unistd.h>
#include <string>
#include <strings.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <pthread.h>

namespace Client
{
    using namespace std;

    class udpClient
    {
    public:
        udpClient(const string &serverip, const uint16_t &serverport)
            : _serverip(serverip),
              _serverport(serverport),
              _sockfd(-1),
              _quit(false)
        {
        }
        void initClient()
        {
            // 创建socket
            _sockfd = socket(AF_INET, SOCK_DGRAM, 0);
            if (_sockfd == -1)
            {
                cerr << "socket error: " << errno << ":" << strerror(errno) << endl;
                exit(2);
            }
            cout << "socket success: " << ":" << _sockfd << endl;
        }

        static void *readMessage(void *args) // 类内创建线程方法，隐含的有this指针，多了一个参数，可以设置为static。static成员函数不能访问私有成员变量
        {
            int sockfd = *(static_cast<int *>(args));
            pthread_detach(pthread_self());
            while (true)
            {
                char buffer[1024];
                struct sockaddr_in tmp;
                socklen_t tmp_len = sizeof(tmp);
                ssize_t n = recvfrom(sockfd, buffer, sizeof(buffer) - 1, 0, (struct sockaddr *)&tmp, &tmp_len);
                if (n > 0)
                {
                    buffer[n] = 0; // 保证字符串是以\0结尾的
                }

                cout << buffer << endl; // cout往1号文件描述符里打印，正好重定向到管道
            }
            return nullptr;
        }
        void run()
        {
            // 客户端，如果一个进程的话，自己发出去的消息，和服务器广播回来的其他用户的消息就混在一起了
            // 使用多进程简单一些，fork创建子进程，文件描述符sockfd是可以被子进程继承的，一个进程发送信息，一个进程读取接收信息。

            // 这里我们不使用多进程的方式，使用多线程来解决

            pthread_create(&_reader, nullptr, readMessage, (void *)&_sockfd); // 64位机器下，指针占八个字节

            struct sockaddr_in server;
            memset(&server, 0, sizeof(server));
            // bzero(&local, sizeof(local));  // 初始化网络地址结构体。将内存区域置0。
            server.sin_family = AF_INET;
            server.sin_addr.s_addr = inet_addr(_serverip.c_str()); // inet_addr(const char*)  string ip->ip.c_str()
            server.sin_port = htons(_serverport);
            // 主线程不断发送消息，新线程不断接收消息。
            string message;
            char cmdline[1024];
            while (!_quit)
            {
                // cout << "#";
                // cerr<<"#";//往2号文件描述符里打印，不会重定向到管道里
                fprintf(stderr, "Enter# ");
                fflush(stderr);
                fgets(cmdline, sizeof(cmdline), stdin);
                cmdline[strlen(cmdline) - 1] = 0; // 本应该输入“online”就上线，但是键盘输入会摁回车。
                message = cmdline;

                sendto(_sockfd, message.c_str(), message.size(), 0, (struct sockaddr *)&server, sizeof(server));
            }
        }
        ~udpClient()
        {
        }

    private:
        int _sockfd;
        string _serverip;
        uint16_t _serverport;
        bool _quit;
        pthread_t _reader;
    };
}
// 如果要进行验证，可以创建一个管道，凡是客户端往显示器上打印的，重定向到管道。